package de.stefanocke.roo.wicket.base.html;

import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.file.monitor.event.FileEvent;
import org.springframework.roo.file.monitor.event.FileEventListener;
import org.springframework.roo.file.monitor.event.FileOperation;
import org.springframework.roo.metadata.MetadataDependencyRegistry;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
import org.springframework.roo.metadata.MetadataItem;
import org.springframework.roo.metadata.MetadataNotificationListener;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.support.logging.HandlerUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;

import de.stefanocke.roo.wicket.base.ItdMetadata;
import de.stefanocke.roo.wicket.base.PathUtil;
import de.stefanocke.roo.wicket.xml.IXmlService;

/**
 * Creates / updates html markup for generated Wicket components. Is triggered
 * by changes in the according ITD metadata, but also by manual changes in the
 * html files.
 * <p>
 * Since we don't want to create a separate MetadataListener for each kind of
 * html to generate, this class is a generic listener with a registry for ITD
 * metadata and the according html builders.
 */
@Component(immediate = true)
@Service
public class HtmlMetadataListener implements MetadataNotificationListener, HtmlBuilderRegistry, FileEventListener {

	@Reference
	private MetadataDependencyRegistry metadataDependencyRegistry;
	@Reference
	private MetadataService metadataService;
	private static final Logger logger = HandlerUtils.getLogger(HtmlMetadataListener.class);
	@Reference
	protected IXmlService xmlService;
	@Reference
	private ProjectOperations projectOperations;

	protected void activate(ComponentContext context) {
		metadataDependencyRegistry.addNotificationListener(this);
	}

	protected void deactivate(ComponentContext context) {
		metadataDependencyRegistry.removeNotificationListener(this);
	}

	private Map<String, Class<? extends HtmlBuilder<? extends MetadataItem>>> metadataClassToBuilderClass = //
	new HashMap<String, Class<? extends HtmlBuilder<? extends MetadataItem>>>();

	// TODO: This probably does not scale so well ...
	private Map<String, String> htmlPathToWicketMetadataId = new HashMap<String, String>();

	@Override
	public void notify(String upstreamDependency, String downstreamDependency) {
		// We only listen to generic notifications, where upstreamDependency
		// identifies an instance and no
		// downstreamDependency is named.
		if (!MetadataIdentificationUtils.isIdentifyingClass(upstreamDependency) && downstreamDependency == null) {

			buildHtml(upstreamDependency);
		}

	}

	@SuppressWarnings("unchecked")
	private <M extends ItdMetadata> void buildHtml(String mid) {
		Validate.isTrue(!MetadataIdentificationUtils.isIdentifyingClass(mid));
		String metadataClass = MetadataIdentificationUtils.getMetadataClass(mid);
		Class<? extends HtmlBuilder<M>> builderClass = (Class<? extends HtmlBuilder<M>>) metadataClassToBuilderClass
				.get(metadataClass);
		if (builderClass != null) {
			// logger.warning("MDR: buildHtml: builderClass: "+builderClass+" used for "+mid);

			M metadataItem = (M) metadataService.get(mid);
			String htmlPath = getHtmlPath(metadataItem);

			if (metadataItem != null) {
				// logger.warning("MDR: buildHtml: ITD metadata: "+metadataItem.getId());
				try {
					HtmlBuilder<M> builder = builderClass.newInstance();
					builder.init(metadataItem);
					Document document = builder.build();

					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					XmlUtils.writeXml(XmlUtils.createIndentingTransformer(), baos, document);
					baos.flush();
					// logger.warning("MDR: document:"+baos.toString());

					htmlPathToWicketMetadataId.put(htmlPath, mid);
					xmlService.writeToDiskIfNecessary(htmlPath, document);

				} catch (Exception e) {
					logger.log(Level.SEVERE, "Error when writing HTML document " + htmlPath + " : " + e.getMessage(), e);
				}
			}

		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.stefanocke.roo.wicket.base.WicketHtmlBuilderRegistry#registerHtmlBuilder
	 * (java.lang.Class, java.lang.Class)
	 */
	@Override
	public <M extends ItdMetadata> void registerHtmlBuilder(Class<M> metadataClass,
			Class<? extends HtmlBuilder<M>> htmlBuilder) {
		// logger.warning("MDR: register "+metadataClass.getName()+" "+htmlBuilder);
		metadataClassToBuilderClass.put(metadataClass.getName(), htmlBuilder);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.stefanocke.roo.wicket.base.WicketHtmlBuilderRegistry#unregisterHtmlBuilder
	 * (java.lang.Class)
	 */
	@Override
	public <M extends ItdMetadata> void unregisterHtmlBuilder(Class<M> metadataClass) {
		metadataClassToBuilderClass.remove(metadataClass.getName());

	}

	private String getHtmlPath(ItdMetadata metadata) {
		
		String mid = metadata.getGovernor().getId();
		
        return projectOperations.getPathResolver()
                .getIdentifier(PathUtil.getResourcesPath(mid), PathUtil.getRelativeFilename(mid, "html"));
	}

	@Override
	public void onFileEvent(FileEvent fe) {
		FileOperation operation = fe.getOperation();
		if (FileOperation.UPDATED == operation || FileOperation.DELETED == operation) {
			String path = fe.getFileDetails().getCanonicalPath();

			String mid = htmlPathToWicketMetadataId.get(path);
			if (mid != null) {
				// logger.warning("MDR: file event "+ fe
				// +". Found according MID: "+mid);
				buildHtml(mid);
			}
		}

	}

}
